﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="MainWindow.xaml.cs" company="TomCorp">
//   Tom
// </copyright>
// <summary>
//   Interaction logic for MainWindow.xaml
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace GeoCoder {
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Collections.Specialized;
    using System.Diagnostics;
    using System.Diagnostics.CodeAnalysis;
    using System.IO;
    using System.Linq;
    using System.Net;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Controls.Ribbon;
    using System.Windows.Data;
    using System.Windows.Input;
    using CsvHelper;
    using ExtensionMethods;    
    using Microsoft.Win32;

    using Model;

    /// <summary>
    ///     Interaction logic for MainWindow.xaml
    /// </summary>
    [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly",
        Justification = "Reviewed. Suppression is OK here.")]
    public partial class MainWindow : RibbonWindow, IProgress<string> {
        /// <summary>
        ///     The herold data.
        /// </summary>
        private readonly ObservableCollection<HeroldData> heroldData = new ObservableCollection<HeroldData>();

        /// <summary>
        ///     The synchronization context.
        /// </summary>
        private readonly SynchronizationContext synchronizationContext = SynchronizationContext.Current;

        /// <summary>
        ///     The web proxy.
        /// </summary>
        private WebProxy webProxy;

        /// <summary>
        ///     Initializes a new instance of the <see cref="MainWindow" /> class.
        /// </summary>
        public MainWindow() {
            this.InitializeComponent();
            Application.Current.ShutdownMode = ShutdownMode.OnMainWindowClose;
            this.LoadingControl.Visibility = Visibility.Hidden;
            var collectionViewSource = CollectionViewSource.GetDefaultView(this.heroldData);
            collectionViewSource.GroupDescriptions.Add(new PropertyGroupDescription("Source"));
            this.ResultsDataGrid.ItemsSource = collectionViewSource;
            this.heroldData.CollectionChanged += this.HeroldDataOnCollectionChanged;
            NetworkUtils.Reporting += this.NetworkUtilsOnReporting;
        }

        /// <summary>
        /// The report.
        /// </summary>
        /// <param name="value">
        /// The value.
        /// </param>
        public void Report(string value) {
            this.synchronizationContext.Post(_ => {
                this.LogTextBox.Text += Environment.NewLine + string.Format("[{0}]: ", DateTime.Now.ToLongTimeString()) + value;
                this.LogTextBox.ScrollToEnd();
            },
            null);
        }

        /// <summary>
        /// The network utils on reporting.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="reportingEventArgs">
        /// The reporting event args.
        /// </param>
        private void NetworkUtilsOnReporting(object sender, NetworkUtils.ReportingEventArgs reportingEventArgs) {
            this.Report(reportingEventArgs.Message);
        }

        /// <summary>
        ///     The search.
        /// </summary>
        /// <returns>
        ///     The <see cref="Task" />.
        /// </returns>
        private async Task Search() {
            try {
                var stopwatch = new Stopwatch();
                stopwatch.Start();

                this.heroldData.Clear();

                if (this.ProxyCheckBox.IsChecked == true) {
                    var host = this.ProxyAddressTextBox.Text.Split(':');
                    this.webProxy = new WebProxy(host[0], int.Parse(host[1]));
                    if (this.ProxyUserTextBox.Text != string.Empty && this.ProxyPasswordBox.Password != string.Empty) {
                        this.webProxy.Credentials = new NetworkCredential(this.ProxyUserTextBox.Text, this.ProxyPasswordBox.Password);
                    }
                }

                var parameterString = this.ParameterTextBox.Text;
                var what = this.WhatTextBox.Text;
                var where = this.WhereComboBox.Text;
                var resultsPerPage = int.Parse(this.ResultsPerPageTextBox.Text);
                var username = this.UsernameTextBox.Text;
                var password = this.PasswordTextBox.Text;

                // ReSharper disable PossibleNullReferenceException
                var searchType = this.FindDescendants<RibbonRadioButton>(e => e.IsChecked == true).FirstOrDefault().Content.ToString();
                // ReSharper restore PossibleNullReferenceException

                // first pass. get the number of total records and the first batch of results
                var parameters = string.Format(parameterString, what, @where, resultsPerPage, 1, (int)DateTime.Now.ToUnixTimeStamp(), searchType);
                var url = this.URLTextBox.Text;

                var tasks = new List<Task<List<HeroldDataSet>>>();

                var result = await NetworkUtils.FetchFromServerAndParse(url, parameters, username, password, this.webProxy);
                foreach (var dataSet in result) {
                    this.Report(string.Format("Total number of expected {0} results: {1}", dataSet.Type == SearchType.PhoneBook ? "phone book" : "yellow pages", dataSet.TotalCount));
                    foreach (var thisresult in dataSet.HeroldData) {
                        thisresult.Source = dataSet.Type == SearchType.PhoneBook ? "Phone Book" : "Yellow Pages";
                        this.heroldData.Add(thisresult);
                    }

                    // fetch the rest
                    var iterations = (int)Math.Ceiling((double)dataSet.TotalCount / resultsPerPage) - 1;
                    if (iterations < 0) continue;

                    // create a new task for every iteration
                    var range = Enumerable.Range(2, iterations);
                    
                    tasks.AddRange(range.Select(
                        i => {
                            parameters = string.Format(parameterString, what, @where, resultsPerPage, i, (int)DateTime.Now.ToUnixTimeStamp(), dataSet.Type == SearchType.PhoneBook ? "wp" : "yp");
                            return NetworkUtils.FetchFromServerAndParse(url, parameters, username, password, this.webProxy);
                        }));
                }

                // wait for the tasks to complete
                var results = await Task.WhenAll(tasks);
                foreach (var heroldDataSets in results) {
                    foreach (var heroldDataSet in heroldDataSets) {
                        foreach (var data in heroldDataSet.HeroldData) {
                            data.Source = heroldDataSet.Type == SearchType.PhoneBook ? "Phone Book" : "Yellow Pages";
                            this.heroldData.Add(data);
                        }
                    }
                }
                ////Parallel.For(
                ////    1,
                ////    iterations,
                ////    async i => {
                ////        parameters = string.Format(
                ////        parameterString,
                ////        what,
                ////        where,
                ////        resultsPerPage,
                ////        i + 1,
                ////        (int)(DateTime.Now - new DateTime(1970, 1, 1).ToLocalTime()).TotalSeconds);
                ////                   tasks[i] = this.FetchFromServerAndParse(url, parameters, username, password);
                ////                   result = await tasks[i - 1];
                ////                   foreach (var thisresult in result.Item2)
                ////                   {
                ////                       heroldData.Add(thisresult);
                ////        }
                ////    });                
                stopwatch.Stop();
                this.Report(string.Format("Parsing {0} records finished. Elapsed time: {1}ms", this.heroldData.Count, stopwatch.ElapsedMilliseconds));
            } catch (WebException webException) {
                this.Report("The server replied with an error to the POST request. The error was \"" + webException.Message + "\"");
            } catch (Exception exception) {
                this.Report("There was an error sending the request. The error was \"" + exception.Message + "\"");
            }
        }

        /// <summary>
        /// The herold data on collection changed.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="notifyCollectionChangedEventArgs">
        /// The notify collection changed event args.
        /// </param>
        private void HeroldDataOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs) {
            if (notifyCollectionChangedEventArgs.Action != NotifyCollectionChangedAction.Add)
                return;
            this.ExportRibbonMenuButton.IsEnabled = true;
            this.VisualizeButton.IsEnabled = true;
        }

        /// <summary>
        /// The ribbon application menu item_ click_1.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        private void ShutdownRibbonApplicationMenuItemClick(object sender, RoutedEventArgs e) {
            Application.Current.Shutdown();
        }

        /// <summary>
        /// The ribbon button_ click_1.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        private async void StartSearchingRibbonButtonClick(object sender, RoutedEventArgs e) {
            this.LoadingControl.Visibility = Visibility.Visible;
            this.LogTextBox.Clear();
            await this.Search();
            this.LoadingControl.Visibility = Visibility.Hidden;
        }

        /// <summary>
        /// The results data grid_ loading row.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        private void ResultsDataGridLoadingRow(object sender, DataGridRowEventArgs e) {
            e.Row.Header = (e.Row.GetIndex() + 1).ToString();
        }

        /// <summary>
        /// The visualize button_ click.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        private void VisualizeButtonClick(object sender, RoutedEventArgs e) {
            var title = string.Format("Visualizing hits for [{0}] in [{1}]", this.WhatTextBox.Text, this.WhereComboBox.Text);
            var visWindow = new VisualizationWindow(title, this.heroldData.ToList());
            visWindow.Show();
        }

        /// <summary>
        /// The ribbon menu item_ click_1.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        private void CopyClipboardRibbonMenuItemClick(object sender, RoutedEventArgs e) {
            this.ResultsDataGrid.SelectAllCells();
            this.ResultsDataGrid.ClipboardCopyMode = DataGridClipboardCopyMode.IncludeHeader;
            ApplicationCommands.Copy.Execute(null, this.ResultsDataGrid);
            this.ResultsDataGrid.UnselectAllCells();
            MessageBox.Show("Data copied to clipboard!");
        }

        /// <summary>
        /// The ribbon menu item_ click_1.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        private void ExportCsvRibbonMenuItemClick(object sender, RoutedEventArgs e) {
            var dlg = new SaveFileDialog {
                FileName = "export",
                DefaultExt = ".csv",
                Filter = "Comma-separated values (.csv)|*.csv"
            };

            if (dlg.ShowDialog() != true)
                return;
            try {
                using (var streamWriter = new StreamWriter(new FileStream(dlg.FileName, FileMode.OpenOrCreate), Encoding.GetEncoding("Windows-1252")))
                using (var csvWriter = new CsvWriter(streamWriter))
                    csvWriter.WriteRecords(this.heroldData);

                MessageBox.Show("Exported data to " + dlg.FileName + " !");
            } catch (Exception exception) {
                MessageBox.Show("There was an error saving the csv file!" + Environment.NewLine + "The error was: " + exception.Message, "Error!", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

        /// <summary>
        /// The help ribbon button click.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        private void HelpRibbonButtonClick(object sender, RoutedEventArgs e) {
            MessageBox.Show("Thomas Heuberger in 2013", "About", MessageBoxButton.OK, MessageBoxImage.Information);
        }

        /// <summary>
        /// The search help ribbon button click.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        private void SearchHintRibbonButtonClick(object sender, RoutedEventArgs e) {
            MessageBox.Show(
                "Herold imposes a hard limit of about 1100 hits returned by a query. "
                + "If you encounter a \"Records expected but none received...\" message in the log "
                + "try to partition your query into smaller subsets, ie. filter by state or city.",
                "Hint",
                MessageBoxButton.OK,
                MessageBoxImage.Information);
        }
    }
}